// 
// Bachelor of Software Engineering
// Media Design School
// Auckland
// New Zealand
//
// (c) 2005 - 2014 Media Design School
//
// File Name	: GameApp.cpp
// Desrciption	: CGameApp implementation file.
// Author(s)	: Hayden Asplet.
// Mail			: hayden.asplet@mediadesignschool.com
//

// PCH
#include "GameStd.h"

// Library Includes
#include <sstream>
#include <fstream>

// This Include
#include "GameApp.h"

// Local Includes

// Globals
CGameApp* g_pApp = NULL;

// Namespaces

using namespace std;

CGameApp::CGameApp()
{
	g_pApp = this;					// This instance is the app of the game.
	m_pGame = NULL;			

	m_bIsRunning = false;			// The game is not running yet.
	m_screenSize = CPoint(0,0);
	m_fDeltaTime = 0;
	m_iFrameRate = 0;

	m_bIsDragging = false;
	m_bIsLMBDown = false;
	m_bIsRMBDown = false;

	m_mousePosition = CPoint(0, 0);
	m_dragStartingPoint = CPoint(0,0);
}

CGameApp::~CGameApp()
{

}

CPoint CGameApp::VGetScreenSize()
{
	return CPoint(g_kiSCREEN_WIDTH, g_kiSCREEN_HEIGHT);
}

DWORD CGameApp::VGetWindowFlags()
{
	return WS_OVERLAPPEDWINDOW | WS_VISIBLE;
}

LPTSTR CGameApp::VGetMenuName()
{
	return NULL;
}

HWND CGameApp::GetHwnd() const
{
	return m_hWnd;
}

HINSTANCE CGameApp::GetInstance() const
{
	return m_hInstance;
}

bool CGameApp::VInitialize(HINSTANCE _hInstance, LPSTR _lpCmdLine, HWND _hWnd, int _iFrameRate)
{
	m_screenSize = VGetScreenSize();
	m_mousePosition = CPoint(-1,-1);
	m_iFrameRate = _iFrameRate;
	m_hInstance = _hInstance;
	
	WNDCLASSEX winClass;

	winClass.cbSize = sizeof(WNDCLASSEX);
	winClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc = MsgProc;
	winClass.cbClsExtra = 0;
	winClass.cbWndExtra = 0;
	winClass.hInstance = m_hInstance;
	winClass.hIcon = VGetIcon();
	winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
	winClass.lpszMenuName = VGetMenuName();
	winClass.lpszClassName = WINDOW_CLASS_NAME;
	winClass.hIconSm = VGetIcon();

	// Register the window class.
	if(!RegisterClassEx(&winClass)) {
	// Failed to register.
		return false;
	}

	// Create the window.
	m_hWnd = CreateWindowEx(NULL,								// Extended style.
							WINDOW_CLASS_NAME,					// Class.
							VGetTitle(),						// Title
							VGetWindowFlags(),	// Flags. 
							0, 0,								// Initial x, y.
							m_screenSize.m_iX, m_screenSize.m_iY,// Initial width, height.
							NULL,								// Handle to parent.
							NULL,								// Handle to menu.
							m_hInstance,						// Instance of this application.
							NULL);								// Extra creation parameters.

	if(!m_hWnd) {
	// Window wasn't created successfully.
		return false;
	}

	m_pGame = VCreateGame();	// Creates and initializes the game.

	if(!m_pGame) {
		return false;
	}

	// Create the renderer.
	m_pRenderer = new CRenderer();

	if(!m_pRenderer) {
		return false;
	}

	// Initialize renderer.
	if(!m_pRenderer->Init(m_hWnd, m_screenSize.m_iX, m_screenSize.m_iY)) {
	// Failed to initialize renderer.
		return false;
	}

	m_bIsRunning = true;	// The game is now running.

	// Return initialization succeeded.
	return true;
}

LRESULT CALLBACK CGameApp::MsgProc(HWND _hWnd, UINT _msg, WPARAM _wParam, LPARAM _lParam)
{
	switch(_msg) {
		case WM_CREATE: {
			// Do initialization here...

			// Return success.
			return 0;
			break;
		}

		case WM_COMMAND: {
			if(g_pApp->VOnCommand(LOWORD(_wParam))) {
				// Return success.
				return 0;
			}
			else {
				break;
			}

		}
		break;
        /*
		 * Handle Mouse Events (JG)
		 */
		case WM_LBUTTONDOWN: // Mouse left button pushed
		{
			// Set mouse state.
			g_pApp->m_bIsLMBDown = true;

			g_pApp->m_pGame->VOnLMBPressed();

			// We might be about to drag, store the current position.
			g_pApp->m_dragStartingPoint = g_pApp->m_mousePosition;	

			// Return success.
			return 0;
		}
		break;
		case WM_LBUTTONUP: // Mouse right button released
		{
			// Set mouse state.
			g_pApp->m_bIsLMBDown = false;
			//g_pApp->m_bIsLMBPressed = false;

			if(g_pApp->m_bIsDragging) {
			// We've been dragging something.
				// Release the dragged object.
				g_pApp->m_pGame->VReleaseDrag();
				g_pApp->m_bIsDragging = false;
			}
			else {
				g_pApp->m_pGame->VOnLMBReleased();
			}

			// Return success.
			return 0;
		}
		case WM_RBUTTONDOWN: // Mouse right button pressed
		{
			// Set mouse state.
			g_pApp->m_bIsRMBDown = true;

			g_pApp->m_pGame->VOnRMBPressed();

			// Return success.
			return 0;
		}
		break;
		case WM_RBUTTONUP: // Mouse right button released
		{
			// Set mouse state.
			g_pApp->m_bIsRMBDown = false;

			g_pApp->m_pGame->VOnRMBReleased();

			// Return success.
			return 0;
		}
		break;
		case WM_LBUTTONDBLCLK: 
		{
			g_pApp->m_pGame->VOnLDoubleClick();

			// Return success.
			return 0;			  
		}
		break;
		case WM_MOUSEMOVE:	// Mouse movement
		{
			// Update the internal representation of mouse position.
			g_pApp->m_mousePosition.m_iX = LOWORD(_lParam);
			g_pApp->m_mousePosition.m_iY = HIWORD(_lParam);

			if(g_pApp->m_bIsLMBDown && !g_pApp->m_bIsDragging) {
				// Get the offset of the current mouse position and when we started dragging.
				CPoint offset = g_pApp->m_mousePosition - g_pApp->m_dragStartingPoint;

				if(abs(offset.m_iX) > g_kiDRAG_THRESHOLD || abs(offset.m_iY) > g_kiDRAG_THRESHOLD) {
				// Offset is beyond the dragging threshold.
					// Start dragging.
					if(g_pApp->m_pGame->VStartDrag(g_pApp->m_dragStartingPoint)) {
					// A drag has started succesfully.
						g_pApp->m_bIsDragging = true;
					}
					else {
					// Failed to start a drag.
					}
				}
			}

			// Return success.
			return 0;
		}
		// End of mouse events

		case WM_PAINT: {
			HDC hdc;
			PAINTSTRUCT ps;

			hdc = BeginPaint(_hWnd, &ps);

			// Do painting stuff here...

			EndPaint(_hWnd, &ps);

			// Return success.
			return 0;
			break;
		}
		
		case WM_SIZE: {
			// Get the new screen size.
			g_pApp->UpdateScreenSize(CPoint(LOWORD(_lParam), HIWORD(_lParam)));

			// Let windows handle the rest.
			break;
		}

		case WM_CLOSE: {
			return g_pApp->OnClose();
			break;
		}

		default: {
			break;
		}

	}

	// Give control back windows to process messages that we did not take care of.
	return DefWindowProc(_hWnd, _msg, _wParam, _lParam); 
}

void CGameApp::Update()
{
	float fStartTime = static_cast<float>(timeGetTime());

	// Clear the display.
	m_pRenderer->Clear();

	if(m_pGame != NULL) {
		// Update the game logic.
		m_pGame->VUpdate(m_fDeltaTime);
	}

	// Render.
	m_pRenderer->Render();

	// Get the time of this tick.
	float fTickTime = static_cast<float>(timeGetTime()) - fStartTime;

	// Get the amount of milliseconds to sleep for to match frame rate.
	int iSleepTime = static_cast<int>((g_kiONE_SECOND / m_iFrameRate) - fTickTime);

	// Fix the time step.
	if(fTickTime < g_kiONE_SECOND / m_iFrameRate) {
		Sleep(iSleepTime);
	}

	m_fDeltaTime = (static_cast<float>(timeGetTime()) - fStartTime) / g_kiONE_SECOND;
}

LRESULT CGameApp::OnClose()
{
	m_bIsRunning = false;

	// Perform clean up.
	if(m_pRenderer) {
		delete m_pRenderer;
	}

	if(m_pGame) {
		delete m_pGame;
	}

	// Kill the application
	DestroyWindow(m_hWnd);
	PostQuitMessage(0);

	// Return success.
	return 0;
}

std::wstring CGameApp::LoadTextFromFile(const std::string& _krFileName)
{
	std::string strTextOfFile;
	std::wstring wsTextOfFile;
	std::stringstream buffer;

	// Open file.
	ifstream ifs(_krFileName.c_str());
	if(!ifs) {
	// Could not open file.
		std::cerr << "ERROR: Couldn't open file \"" << _krFileName << "\"" << std::endl;
		return wsTextOfFile;
	}

	// Gets the text including whitespace and newline characters.
	buffer << ifs.rdbuf();
	strTextOfFile = buffer.str();

	// Close the stream.
	ifs.close();

	wsTextOfFile.assign(strTextOfFile.begin(), strTextOfFile.end());

	return wsTextOfFile;
}

void CGameApp::RenderText(const std::wstring& _krText, const CPoint& _krPosition)
{
	// Set the background of the text to transparent.
	SetBkMode(m_pRenderer->GetDCBuffer(), TRANSPARENT);

	// Set the colour to black.
	SetTextColor(m_pRenderer->GetDCBuffer(), RGB(0, 0, 0));

	// Draw the text.
	TextOut(m_pRenderer->GetDCBuffer(), _krPosition.m_iX, _krPosition.m_iY, _krText.c_str(), wcslen(_krText.c_str()));
}

CSprite CGameApp::LoadSprite(int _id)
{
	return CSprite(LoadBitmap(m_hInstance, MAKEINTRESOURCE(_id)));
}

void CGameApp::RenderSprite(const CSprite& _krSprite, int _iX, int _iY, int _iFrame)
{
	// Get the mask of the frame.
	CRect mask = _krSprite.GetMask(_iFrame);

	// Create the blend functions 
	BLENDFUNCTION BlendFunction;
	BlendFunction.BlendOp = AC_SRC_OVER;
	BlendFunction.BlendFlags = 0;
	BlendFunction.SourceConstantAlpha = g_kiOPAQUE;
	BlendFunction.AlphaFormat = AC_SRC_ALPHA;

	SelectObject(m_pRenderer->GetDCMem(), _krSprite.GetBitmap());

	// Blit the sprite to the screen.
	AlphaBlend(m_pRenderer->GetDCBuffer(), 
			  _iX, _iY, 
			  mask.m_iWidth, mask.m_iHeight, 
			  m_pRenderer->GetDCMem(), 
			  mask.m_iX, mask.m_iY, 
			  mask.m_iWidth, mask.m_iHeight, 
			  BlendFunction);
}

void CGameApp::UpdateScreenSize(const CPoint& _screenSize)
{
	m_screenSize = _screenSize;

	if(m_pRenderer) {
		m_pRenderer->SetScreenSize(m_screenSize);
	}
}

CPoint CGameApp::GetScreenSize() const
{
	return m_screenSize;
}

bool CGameApp::IsRunning() const
{
	return m_bIsRunning;
}

CPoint CGameApp::GetMousePosition() const
{
	return m_mousePosition;
}

bool CGameApp::IsDragging() const
{
	return m_bIsDragging;
}